package springdao.support;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.jdo.JDOException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.jdo.JdoCallback;
import org.springframework.orm.jdo.support.JdoDaoSupport;
import springdao.DaoRepository;

/**
 * @author Kent Yeh
 */
public abstract class AbstractSpringDao<E> extends JdoDaoSupport implements DaoRepository<E> {

    private static Logger logger = LoggerFactory.getLogger(AbstractSpringDao.class);
    private boolean jpql = false;

    @Override
    public boolean isJpql() {
        return jpql;
    }

    @Override
    public void setJpql(boolean jpql) {
        this.jpql = jpql;
    }

    public List<E> findByJPQL(final String jpql, final int startPageNo, final int pageSize) {
        return getJdoTemplate().execute(new JdoCallback<List<E>>() {

            public List<E> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = pm.newQuery("javax.jdo.query.JPQL", jpql);
                prepareQuery(query);
                if (startPageNo > 0 && pageSize > 0) {
                    int from = (startPageNo - 1) * pageSize;
                    int to = from + pageSize;
                    query.setRange(from, to);
                }
                return (List<E>) query.execute();
            }
        });
    }

    public List<E> findByJPQL(final String jpql, final int startPageNo, final int pageSize, final Object... parameters) {
        return getJdoTemplate().execute(new JdoCallback<List<E>>() {

            public List<E> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = pm.newQuery("javax.jdo.query.JPQL", jpql);
                prepareQuery(query);
                if (startPageNo > 0 && pageSize > 0) {
                    int from = (startPageNo - 1) * pageSize;
                    int to = from + pageSize;
                    query.setRange(from, to);
                }
                return (List<E>) query.execute(parameters);
            }
        });
    }

    public List<E> findByJPQL(final String jpql, final int startPageNo, final int pageSize, final Map<String, ?>... parameters) {
        return getJdoTemplate().execute(new JdoCallback<List<E>>() {

            public List<E> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = pm.newQuery("javax.jdo.query.JPQL", jpql);
                prepareQuery(query);
                if (startPageNo > 0 && pageSize > 0) {
                    int from = (startPageNo - 1) * pageSize;
                    int to = from + pageSize;
                    query.setRange(from, to);
                }
                return (List<E>) query.execute(parameters);
            }
        });
    }

    public void prepareQuery(Query query) throws JDOException {
        getJdoTemplate().prepareQuery(query);
    }

    @Override
    public int bulkUpdate(final String QL) {
        return getJdoTemplate().execute(new JdoCallback<Long>() {

            public Long doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                return (Long) query.execute();
            }
        }).intValue();
    }

    @Override
    public List<Integer> bulkUpdate(final List<String> QLs) {
        return getJdoTemplate().execute(new JdoCallback<List<Integer>>() {

            public List<Integer> doInJdo(PersistenceManager pm) throws JDOException {
                List<Integer> res = new ArrayList<Integer>();
                for (String ql : QLs) {
                    Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", ql) : pm.newQuery(ql);
                    prepareQuery(query);
                    res.add(((Long) query.execute()).intValue());
                }
                return res;
            }
        });
    }

    @Override
    public int bulkUpdate(final String QL, final Object... parameters) {
        return getJdoTemplate().execute(new JdoCallback<Long>() {

            public Long doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                return (Long) query.executeWithArray(parameters);
            }
        }).intValue();
    }

    @Override
    public int bulkUpdate(final String QL, final Map<String, ?> parameters) {
        return getJdoTemplate().execute(new JdoCallback<Long>() {

            public Long doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                return (Long) query.executeWithMap(parameters);
            }
        }).intValue();
    }

    @Override
    public void clear() {
//        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean contains(Object entity) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void delete(E entity) {
        getJdoTemplate().deletePersistent(entity);
        getJdoTemplate().flush();
    }

    @Override
    public void delete(E entity, String lockMode) {
        delete(entity);
    }

    @Override
    public void delete(Collection<E> entities) {
        getJdoTemplate().deletePersistentAll(entities);
        getJdoTemplate().flush();
    }

    private List<E> toList(Collection<E> collection) {
        return collection == null || collection.isEmpty() ? new ArrayList<E>() : new ArrayList<E>(collection);
    }

    @Override
    public List<E> findByCriteria(String qlCriteria) {
        if (isJpql()) {
            return findByJPQL(new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria).toString(), 0, 0);
        } else {;
            Collection<E> finds = getJdoTemplate().detachCopyAll((Collection<E>) getJdoTemplate().find(
                    new StringBuilder("SELECT FROM ").append(getEntityName()).append(" ").append(qlCriteria).toString()));
            return toList(finds);
        }
    }

    @Override
    public List<E> findByCriteria(String qlCriteria, Object... parameters) {
        if (isJpql()) {
            return findByJPQL(new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria).toString(), 0, 0, parameters);
        } else {
            Collection<E> finds = getJdoTemplate().detachCopyAll((Collection<E>) getJdoTemplate().find(
                    new StringBuilder("SELECT FROM ").append(getEntityName()).append(" ").append(qlCriteria).toString(), parameters));
            return toList(finds);
        }
    }

    @Override
    public List<E> findByCriteria(String qlCriteria, Map<String, ?> parameters) {
        if (isJpql()) {
            return findByJPQL(new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria).toString(), 0, 0, parameters);
        } else {
            Collection<E> finds = getJdoTemplate().detachCopyAll((Collection<E>) getJdoTemplate().find(
                    new StringBuilder("SELECT FROM ").append(getEntityName()).append(" ").append(qlCriteria).toString(), parameters));
            return toList(finds);
        }
    }

    @Override
    public List<E> findByCriteria(String qlCriteria, int startPageNo, int pageSize, Object... parameters) {
        if ((startPageNo < 1) || (pageSize < 1)) {
            return findByCriteria(qlCriteria, parameters);
        } else if (parameters == null || parameters.length == 0) {
            return findByCriteria(qlCriteria, startPageNo, pageSize);
        } else {
            int from = (startPageNo - 1) * pageSize;
            int to = from + pageSize;
            StringBuilder sb = isJpql() ? new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria)
                    : new StringBuilder("SELECT FROM ").append(getEntityName()).append(" ").append(qlCriteria).
                    append(" range ").append(from).append(",  ").append(to);
            return isJpql() ? findByJPQL(sb.toString(), startPageNo, pageSize, parameters)
                    : toList(getJdoTemplate().detachCopyAll((Collection< E>) getJdoTemplate().find(sb.toString(), parameters)));
        }
    }

    @Override
    public List<E> findByCriteria(String qlCriteria, int startPageNo, int pageSize, Map<String, ?> parameters) {
        if ((startPageNo < 1) || (pageSize < 1)) {
            return findByCriteria(qlCriteria, parameters);
        } else if (parameters == null || parameters.isEmpty()) {
            return findByCriteria(qlCriteria, startPageNo, pageSize);
        } else {
            int from = (startPageNo - 1) * pageSize;
            int to = from + pageSize;
            StringBuilder sb = isJpql() ? new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria)
                    : new StringBuilder("SELECT FROM ").append(getEntityName()).append(" ").append(qlCriteria).
                    append(" range ").append(from).append(",  ").append(to);
            return isJpql() ? findByJPQL(sb.toString(), startPageNo, pageSize, parameters)
                    : toList(getJdoTemplate().detachCopyAll((Collection<E>) getJdoTemplate().find(sb.toString(), parameters)));
        }
    }

    @Override
    public List<E> findByCriteria(String qlCriteria, int startPageNo, int pageSize) {
        if ((startPageNo < 1) || (pageSize < 1)) {
            return findByCriteria(qlCriteria);
        } else {
            int from = (startPageNo - 1) * pageSize;
            int to = from + pageSize;
            StringBuilder sb = isJpql() ? new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria)
                    : new StringBuilder(qlCriteria).append(" range ").append(from).append(",  ").append(to);
            return isJpql() ? findByJPQL(qlCriteria, startPageNo, pageSize) : findByCriteria(sb.toString());
        }
    }

    @Override
    public List<E> findByJoinCriteria(String joins, String qlCriteria) {
        if (isJpql()) {
            StringBuilder sb = new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria);
            return findByJPQL(sb.toString(), 0, 0);
        } else {
            throw new UnsupportedOperationException("Not support JDO Join query criteria.");
        }
    }

    @Override
    public List<E> findByJoinCriteria(String joins, String qlCriteria, Object... parameters) {
        if (isJpql()) {
            StringBuilder sb = new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria);
            return findByJPQL(sb.toString(), 0, 0, parameters);
        } else {
            throw new UnsupportedOperationException("Not support JDO Join query criteria.");
        }
    }

    @Override
    public List<E> findByJoinCriteria(String joins, String qlCriteria, Map<String, ?> parameters) {
        if (isJpql()) {
            StringBuilder sb = new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria);
            return findByJPQL(sb.toString(), 0, 0, parameters);
        } else {
            throw new UnsupportedOperationException("Not support JDO Join query criteria.");
        }
    }

    @Override
    public List<E> findByJoinCriteria(String joins, String qlCriteria, int startPageNo, int pageSize, Object... parameters) {
        if (isJpql()) {
            StringBuilder sb = new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria);
            return findByJPQL(sb.toString(), startPageNo, pageSize, parameters);
        } else {
            throw new UnsupportedOperationException("Not support JDO Join query criteria.");
        }
    }

    @Override
    public List<E> findByJoinCriteria(String joins, String qlCriteria, int startPageNo, int pageSize, Map<String, ?> parameters) {
        if (isJpql()) {
            StringBuilder sb = new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria);
            return findByJPQL(sb.toString(), startPageNo, pageSize, parameters);
        } else {
            throw new UnsupportedOperationException("Not support JDO Join query criteria.");
        }
    }

    @Override
    public List<E> findByJoinCriteria(String joins, String qlCriteria, int startPageNo, int pageSize) {
        if (isJpql()) {
            StringBuilder sb = new StringBuilder("SELECT ").append(getAliasName()).append(" FROM ").
                    append(getEntityName()).append("  ").append(getAliasName()).append(" ").append(qlCriteria);
            return findByJPQL(sb.toString(), startPageNo, pageSize);
        } else {
            throw new UnsupportedOperationException("Not support JDO Join query criteria.");
        }
    }

    @Override
    public List<E> findByNamedQuery(String name) {
        return new ArrayList<E>(getJdoTemplate().detachCopyAll(getJdoTemplate().findByNamedQuery(getClazz(), name)));
    }

    @Override
    public List<E> findByNamedQuery(String name, Object... parameters) {
        return new ArrayList<E>(getJdoTemplate().detachCopyAll(getJdoTemplate().findByNamedQuery(getClazz(), name, parameters)));
    }

    @Override
    public List<E> findByNamedQuery(String name, Map<String, ?> parameters) {
        return new ArrayList<E>(getJdoTemplate().detachCopyAll(getJdoTemplate().findByNamedQuery(getClazz(), name, parameters)));
    }

    @Override
    public E findByPrimaryKey(Serializable primaryKey) {
        return getJdoTemplate().getObjectById(getClazz(), primaryKey);
    }

    /**
     * Not support LockMode
     */
    @Override
    public E findByPrimaryKey(Serializable primaryKey, String lockMode) {
        return getJdoTemplate().getObjectById(getClazz(), primaryKey);
    }

    /**
     * Not support properties mode.
     */
    @Override
    public E findByPrimaryKey(Serializable primaryKey, Map<String, Object> properties) {
        return getJdoTemplate().getObjectById(getClazz(), primaryKey);
    }

    /**
     * Not support LockMode or properties
     */
    @Override
    public E findByPrimaryKey(Serializable primaryKey, String lockMode, Map<String, Object> properties) {
        return getJdoTemplate().getObjectById(getClazz(), primaryKey);
    }

    @Override
    public List<E> findBySQLQuery(final String sql) {
        return getJdoTemplate().execute(new JdoCallback<List<E>>() {

            public List<E> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = pm.newQuery(Query.SQL, sql);
                prepareQuery(query);
                return (List<E>) query.execute();
            }
        });
    }

    @Override
    public List<E> findBySQLQuery(final String sql, final Object... parameters) {
        return getJdoTemplate().execute(new JdoCallback<List<E>>() {

            public List<E> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = pm.newQuery(Query.SQL, sql);
                prepareQuery(query);
                query.setClass(getClazz());
                return (List<E>) query.executeWithArray(parameters);
            }
        });
    }

    @Override
    public List<E> findBySQLQuery(final String sql, final Map<String, ?> parameters) {
        return getJdoTemplate().execute(new JdoCallback<List<E>>() {

            public List<E> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = pm.newQuery(Query.SQL, sql);
                prepareQuery(query);
                query.setClass(getClazz());
                return (List<E>) query.executeWithMap(parameters);
            }
        });
    }

    @Override
    public List<E> findBySQLQuery(String sql, String entityAlias) {
        return findBySQLQuery(sql);
    }

    @Override
    public List<E> findBySQLQuery(String sql, String entityAlias, Object... parameters) {
        return findBySQLQuery(sql, parameters);
    }

    @Override
    public List<E> findBySQLQuery(String sql, String entityAlias, Map<String, ?> parameters) {
        return findBySQLQuery(sql, parameters);
    }

    @Override
    public <T> T findUniqueByQL(final Class<T> clazz, final String QL) {
        return getJdoTemplate().execute(new JdoCallback<T>() {

            public T doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                query.setResultClass(clazz);
                query.setUnique(true);
                return (T) query.execute();
            }
        });
    }

    @Override
    public <T> T findUniqueByQL(final Class<T> clazz, final String QL, final Object... parameters) {
        return getJdoTemplate().execute(new JdoCallback<T>() {

            public T doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                query.setResultClass(clazz);
                query.setUnique(true);
                return (T) query.executeWithArray(parameters);
            }
        });
    }

    @Override
    public <T> T findUniqueByQL(final Class<T> clazz, final String QL, final Map<String, ?> parameters) {
        return getJdoTemplate().execute(new JdoCallback<T>() {

            public T doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                query.setResultClass(clazz);
                query.setUnique(true);
                return (T) query.executeWithMap(parameters);
            }
        });
    }

    @Override
    public <T> List<T> findListByQL(final Class<T> clazz, final String QL) {
        return getJdoTemplate().execute(new JdoCallback<List<T>>() {

            public List<T> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                return (List<T>) query.execute();
            }
        });
    }

    @Override
    public <T> List<T> findListByQL(final Class<T> clazz, final String QL, final Object... parameters) {
        return getJdoTemplate().execute(new JdoCallback<List<T>>() {

            public List<T> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                return (List<T>) query.executeWithArray(parameters);
            }
        });
    }

    @Override
    public <T> List<T> findListByQL(final Class<T> clazz, final String QL, final Map<String, ?> parameters) {
        return getJdoTemplate().execute(new JdoCallback<List<T>>() {

            public List<T> doInJdo(PersistenceManager pm) throws JDOException {
                Query query = isJpql() ? pm.newQuery("javax.jdo.query.JPQL", QL) : pm.newQuery(QL);
                prepareQuery(query);
                return (List<T>) query.executeWithMap(parameters);
            }
        });
    }

    @Override
    public <T> List<T> findListByNamedQuery(Class<T> clazz, String name) {
        return new ArrayList<T>(getJdoTemplate().detachCopyAll(getJdoTemplate().findByNamedQuery(clazz, name)));
    }

    public <T> List<T> findListByNamedQuery(Class<T> clazz, String name, Object... parameters) {
        return new ArrayList<T>(getJdoTemplate().detachCopyAll(getJdoTemplate().findByNamedQuery(clazz, name, parameters)));
    }

    @Override
    public <T> List<T> findListByNamedQuery(Class<T> clazz, String name, Map<String, ?> parameters) {
        return new ArrayList<T>(getJdoTemplate().detachCopyAll(getJdoTemplate().findByNamedQuery(clazz, name, parameters)));
    }

    @Override
    public String getAliasName() {
        if (isJpql()) {
            return AliasHelper.getAlias(getClazz());
        } else {
            return AliasHelper.getAlias(getClazz());
        }
    }

    @Override
    public String getEntityName() {
        return getClazz().getName();
    }

    @Override
    public E initLazyCollection(final E entity, final String fetchGroupName) {
        return getJdoTemplate().execute(new JdoCallback<E>() {

            public E doInJdo(PersistenceManager pm) throws JDOException {
                pm.makePersistent(entity);
                pm.getFetchPlan().addGroup(fetchGroupName);
                return pm.detachCopy(entity);
            }
        });
    }

    @Override
    public E instanate() throws InstantiationException, IllegalAccessException {
        return getJdoTemplate().execute(new JdoCallback<E>() {

            public E doInJdo(PersistenceManager pm) throws JDOException {
                return pm.newInstance(getClazz());
            }
        });
    }

    @Override
    public E lock(E entity, String lockMode) {
        return entity;
    }

    @Override
    public E lock(String entityName, E entity, String lockMode) {
        return entity;
    }

    @Override
    public E merge(E entity) {
        return save(entity);
    }

    @Override
    public Collection<E> merge(Collection<E> entities) {
        return save(entities);
    }

    @Override
    public E merge(String entityName, E entity) {
        return save(entity);
    }

    @Override
    public E persist(E entity) {
        return save(entity);
    }

    @Override
    public E persist(String entityName, E entity) {
        return save(entity);
    }

    @Override
    public E refresh(E entity) {
        getJdoTemplate().refresh(entity);
        return entity;
    }

    @Override
    public E refresh(E entity, String lockMode) {
        return refresh(entity);
    }

    @Override
    public E save(E entity) {
        entity = getJdoTemplate().makePersistent(entity);
        getJdoTemplate().flush();
        return entity;
    }

    @Override
    public Collection<E> save(Collection<E> entities) {
        entities = getJdoTemplate().makePersistent(entities);
        getJdoTemplate().flush();
        return entities;
    }

    @Override
    public E saveOrUpdate(E entity) {
        return save(entity);
    }

    @Override
    public E saveOrUpdate(String entityName, E entity) {
        return save(entity);
    }

    @Override
    public Collection<E> saveOrUpdate(Collection<E> entities) {
        return save(entities);
    }

    @Override
    public E update(E entity) {
        return save(entity);
    }

    @Override
    public E update(E entity, String lockMode) {
        return save(entity);
    }

    @Override
    public Collection<E> update(Collection<E> entities) {
        return save(entities);
    }
}
